package com.globallogic.jira.gadget.servlet;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;

import com.atlassian.jira.ComponentManager;
import com.atlassian.jira.config.SubTaskManager;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.link.IssueLink;
import com.atlassian.jira.issue.link.IssueLinkManager;
import com.atlassian.jira.issue.search.SearchException;
import com.atlassian.jira.project.Project;
import com.atlassian.templaterenderer.TemplateRenderer;
import com.globallogic.jira.gadget.composite.ReportNode;
import com.globallogic.jira.gadget.utils.Search;

/**
 * An class that builds the tree structure from reports and then delegate
 * rendering to needed velocity template. Depending on parameter it can be
 * gadget or report.
 * 
 * @author anton.pereverziev
 * @author yegor.chymakov
 */

@SuppressWarnings("serial")
public class TreeBuilderServlet extends HttpServlet {

	private final int MAX_NESTING_LEVEL = 10;
	private final String SUBTASK_LINK = "jira_subtask_link";
	private static final Logger log = Logger
			.getLogger(TreeBuilderServlet.class);
	private final IssueLinkManager issueLinkManager;
	private final TemplateRenderer templateRenderer;

	/**
	 * Servlet constructor that is auto-wired by Pico Container to include the
	 * following services registered in the params.
	 * 
	 * @param templateRenderer
	 * @param issueLinkManager
	 */
	public TreeBuilderServlet(final TemplateRenderer templateRenderer,
			final IssueLinkManager issueLinkManager) {
		this.templateRenderer = templateRenderer;
		this.issueLinkManager = issueLinkManager;
	}

	@SuppressWarnings("unchecked")
	@Override
	protected void doGet(final HttpServletRequest req,
			final HttpServletResponse resp) throws ServletException,
			IOException {
		final String projectName = req.getParameter("projectName");
		final String fixVersion = req.getParameter("fixVersion");
		final String issueType = req.getParameter("issueType");
		final String linkType = req.getParameter("linkType");
		final String[] linkTypes = linkType == null ? null : linkType
				.split(",");
		final String includeSubtasks = req.getParameter("includeSubtasks");
		final String includeOrphans = req.getParameter("includeOrphans");
		final String isNode = req.getParameter("is_node");
		final String isBranch = req.getParameter("is_branch");
		final String issueKey = req.getParameter("issue_key");
		final String path = req.getContextPath();

		log.info("\n*** Parameters from request: ***\n " + "\tprojectName="
				+ projectName + "\n\tissueType=" + issueType
				+ "\n\tfixVersion=" + fixVersion + "\n\tType="
				+ req.getParameter("linkType") + "\n\tincludeSubtasks="
				+ includeSubtasks + "\n\tincludeOrphans=" + includeOrphans
				+ "\n\tsummary_word=" + isBranch + "\n\tis_node=" + isNode);

		log.info("\n---------------->Servlet path=" + req.getContextPath());

		List<Object> objects = null;
		List<Issue> roots = null;
		Set<Issue> unincludedIssues = null;
		ReportNode root = null;
		String errorMessage = null;
		final ReportNode orphansRoot = new ReportNode(null);
		if (isNode != null && isNode.equals("true")) {

			final IssueManager issueManager = ComponentManager.getInstance()
					.getIssueManager();
			final Issue issue = issueManager.getIssueObject(issueKey);
			if (issue == null) {
				log.error("Cannot get issue with key=" + issueKey);
				return;
			}
			root = new ReportNode(issue);

		} else {
			final Project project = ComponentManager.getInstance()
					.getProjectManager().getProjectObjByName(projectName);
			if (project == null) {
				errorMessage = "Cannot find project";
				log.error("Cannot find project");
				root = new ReportNode();
			} else {
				final Long pid = project.getId();
				try {
					objects = Search.getIssuesFromProject(pid, issueType,
							fixVersion, Boolean.parseBoolean(includeOrphans),
							Boolean.parseBoolean(includeSubtasks));
					roots = (List<Issue>) objects.get(0);
					unincludedIssues = (Set<Issue>) objects.get(1);
					if (log.isDebugEnabled())
						log.trace("\n------------>Found " + roots.size()
								+ " root elements");
				} catch (final SearchException e) {
					log.error("\n------------> Error while searching root elements: "
							+ e.getMessage());

					e.printStackTrace();
					return;
				}
				root = buildTree(roots, unincludedIssues, linkTypes,
						Boolean.parseBoolean(includeSubtasks),
						Boolean.parseBoolean(includeOrphans), orphansRoot);

				// for (ReportNode reportNode : )

				for (final Issue is : unincludedIssues) {
					orphansRoot.addNode(ReportNode.createRootNode(is));
				}
			}
		}

		if (root.getChildren().isEmpty()
				&& ("false".equals(includeOrphans) || ("true"
						.equals(includeOrphans) && orphansRoot.getChildren()
						.isEmpty()))) {
			errorMessage = "No issues found";
		}

		final Map<String, Object> map = new HashMap<String, Object>();
		map.put("root", root);
		map.put("orphansRoot", orphansRoot);
		map.put("projectName", projectName);
		map.put("issueType", issueType);
		map.put("fixVersion", fixVersion);
		map.put("linkTypes", linkTypes);
		map.put("includeSubtasks", includeSubtasks);
		map.put("includeOrphans", includeOrphans);
		map.put("path", path);
		map.put("isBranch", isBranch);
		map.put("errorMessage", errorMessage);
		if (isNode != null && isNode.equals("true"))
			templateRenderer.render("node.vm", map, resp.getWriter());
		else
			templateRenderer.render("gadget.vm", map, resp.getWriter());
	}

	/**
	 * Build a tree like structure from list of issue, filter the issues by
	 * types, so only correct hierarchy will be later rendered.
	 * 
	 * @param orphansRoot
	 * 
	 * @param all
	 * @return
	 */
	private ReportNode buildTree(final List<Issue> issues,
			final Set<Issue> unincludedIssues, final String[] issueLinkType,
			final boolean includeSubTasks, final boolean includeOrphans,
			final ReportNode orphansRoot) {
		final ReportNode root = new ReportNode(null);
		ReportNode node = null;
		for (final Issue issue : issues) {
			node = buildBranch(issue, unincludedIssues, 0, issueLinkType,
					includeSubTasks, "Root element");
			node.setRoot(true);
			if (node.getTreeLength() == 0)
				orphansRoot.addNode(node);
			else
				root.addNode(node);
		}
		return root;
	}

	/**
	 * Collects data and builds n-ary tree like structure from issues linked
	 * between each other.
	 * 
	 * @param root
	 * @param level
	 * @param issueLinkType
	 * @param includeSubTasks
	 * @return
	 */
	private ReportNode buildBranch(final Issue root,
			final Set<Issue> unincludedIssues, final int level,
			final String[] issueLinkType, final boolean includeSubTasks,
			final String currentLinkType) {
		final ReportNode node = new ReportNode(root);
		node.setLinkType(currentLinkType);
		final List<IssueLink> links = issueLinkManager.getInwardLinks(root
				.getId());
		for (final IssueLink issueLink : links) {
			if (level > MAX_NESTING_LEVEL) {
				node.setOverflow(true);
				return node;
			}

			if (ArrayUtils.contains(issueLinkType, "All")
					|| ArrayUtils.contains(issueLinkType, issueLink
							.getIssueLinkType().getName())) {
				final Issue issue = issueLink.getSourceObject();
				unincludedIssues.remove(issue);
				if (issue.isSubTask()) {
					node.addNode(buildBranchExcludeSubtasks(issue,
							unincludedIssues, level + 1, issueLinkType,
							includeSubTasks));
				} else {
					node.addNode(buildBranch(issue, unincludedIssues,
							level + 1, issueLinkType, includeSubTasks,
							issueLink.getIssueLinkType().getName()));
				}
			}
		}
		if (includeSubTasks) {
			if (level > MAX_NESTING_LEVEL) {
				node.setOverflow(true);
				return node;
			}
			final SubTaskManager subTaskManager = ComponentManager
					.getInstance().getSubTaskManager();
			final List<IssueLink> subTasks = subTaskManager
					.getSubTaskIssueLinks(root.getId());
			for (final IssueLink issueLink : subTasks) {
				final Issue issue = issueLink.getDestinationObject();
				unincludedIssues.remove(issue);
				node.addNode(buildBranchExcludeSubtasks(issue,
						unincludedIssues, level + 1, issueLinkType,
						includeSubTasks));
			}
		}

		return node;
	}

	private ReportNode buildBranchExcludeSubtasks(final Issue root,
			final Set<Issue> unincludedIssues, final int level,
			final String issueLinkType[], final boolean includeSubTasks) {
		final ReportNode node = new ReportNode(root);
		node.setLinkType("Sub-Task");

		final List<IssueLink> links = issueLinkManager.getInwardLinks(root
				.getId());
		for (final IssueLink issueLink : links) {
			if (level > MAX_NESTING_LEVEL) {
				node.setOverflow(true);
				return node;
			}
			if (ArrayUtils.contains(issueLinkType, "All")
					|| ArrayUtils.contains(issueLinkType, issueLink
							.getIssueLinkType().getName())) {
				if (!issueLink.getIssueLinkType().getName()
						.equals(SUBTASK_LINK)) {
					final Issue issue = issueLink.getSourceObject();
					unincludedIssues.remove(issue);
					if (issueLink.getIssueLinkType().getName()
							.equals(SUBTASK_LINK))
						node.addNode(buildBranchExcludeSubtasks(issue,
								unincludedIssues, level + 1, issueLinkType,
								includeSubTasks));
					else {
						node.addNode(buildBranch(issue, unincludedIssues,
								level + 1, issueLinkType, includeSubTasks,
								issueLink.getIssueLinkType().getName()));
					}
				}
			}
		}
		return node;
	}
}